#include "stdafx.hpp"
#include "ColligereDatabaseItemBase.hpp"
#include "ColligereUtils.hpp"

///////////////////////////////////////
// DatabaseItemState
///////////////////////////////////////

col::DatabaseItemState::DatabaseItemState()  : mStateFlags ( None ) 
{ 
}

#ifdef __WXDEBUG__
const wxString col::DatabaseItemState::ToString ( wxUint32 state )
{
	wxString state_str;

	if ( state & Added )
	{
		state_str += "ADDED / ";
	}
	if ( state & Deleted )
	{
		state_str += "DELETED / ";
	}
	if ( state & Changed )
	{
		state_str += "CHANGED / ";
	}
	if ( state & None )
	{
		state_str += "NONE / ";
	}
	if ( state & ChildAdded )
	{
		state_str += "CHILD ADDED / ";
	}
	if ( state & ChildChanged )
	{
		state_str += "CHILD CHANGED / ";
	}
	if ( state & ChildDeleted )
	{
		state_str += "CHILD DELETED";
	}
	return state_str;
}
#endif // __WXDEBUG__

void col::DatabaseItemState::Set ( wxUint32 state )
{
	wxASSERT ( state < ErrMax && state > ErrMin );
	mStateFlags = state;
}

bool col::DatabaseItemState::IsActive ( wxUint32 state ) const
{
	wxASSERT ( state < ErrMax && state > ErrMin );
	wxASSERT ( state != None );

#ifdef __WXDEBUG__
	const bool is_active = ( mStateFlags & state ) != 0;
	//wxLogDebug ( "State %s active.", is_active ? "was" : "was not" );
	return is_active;
#else
	return ( mStateFlags & state ) != 0;
#endif
}

void col::DatabaseItemState::Add ( wxUint32 state )
{
	wxASSERT ( state < ErrMax && state > ErrMin );
	wxASSERT_MSG ( state != None, "Cannot add the state NONE to "\
		"other states!" );

	if ( IsActive ( Added ) && state == Changed )
	{
		return;
	}
	mStateFlags |= state;

	wxASSERT ( mStateFlags < ErrMax && mStateFlags > ErrMin );
}

void col::DatabaseItemState::Remove ( wxUint32 state )
{
	wxASSERT ( state < ErrMax && state > ErrMin );
	wxASSERT_MSG ( state != None, "Cannot remove the NONE state!" );
	mStateFlags &= ~state;
	wxASSERT ( mStateFlags < ErrMax && mStateFlags > ErrMin );
}

bool col::DatabaseItemState::IsNone() const
{
	return mStateFlags == None;
}

wxUint32 col::DatabaseItemState::Get() const 
{ 
	return mStateFlags; 
}

bool col::DatabaseItemState::operator == ( wxUint32 state ) const 
{ 
	return Get() == state; 
}

col::DatabaseItemState &col::DatabaseItemState::operator= ( wxUint32 state ) 
{ 
	Set ( state ); 
	return *this; 
}

col::DatabaseItemState &col::DatabaseItemState::operator+= ( wxUint32 state ) 
{ 
	Add ( state ); 
	return *this; 
}

col::DatabaseItemState &col::DatabaseItemState::operator-= ( wxUint32 state ) 
{ 
	Remove ( state ); 
	return *this; 
}

///////////////////////////////////////
// DatabaseItemData
///////////////////////////////////////

bool col::DatabaseItemData::SetColumnValue ( wxUint32 indx, long value )
{
	return SetColumnValue ( indx, wxString() << value );
}

bool col::DatabaseItemData::SetColumnValue ( wxUint32 indx, const wxString &rValue )
{
	ColumnValueMap::iterator it ( mValues.find ( indx ) );

	if ( it != mValues.end() && it->second != rValue )
	{
		wxString val ( rValue );
		val.Trim().Trim ( false );
		it->second = val;
		SetColumnAsOutdated ( indx );
		return true;
	}
	else if ( it == mValues.end() && !rValue.empty() )
	{
		wxString val ( rValue );
		val.Trim().Trim ( false );
		mValues[indx] = val;
		SetColumnAsOutdated ( indx );
		return true;
	}
	return false;
}

void col::DatabaseItemData::SetColumnAsOutdated ( wxUint32 index )
{
	/*
	wxLogDebug ( "Setting the %s's (%d) column index %d as outdated.",
		GetTableDescriptor().GetItemName().c_str(), mRuntimeId, index );
	*/
	mOutdatedColumns.insert ( index );
}

bool col::DatabaseItemData::IsColumnOutdated ( wxUint32 index ) const
{
	return mOutdatedColumns.find ( index ) != mOutdatedColumns.end();
}

const wxString &col::DatabaseItemData::GetColumnValue 
( wxUint32 indx, const wxString &rDefaultValue ) const
{
	ColumnValueMap::const_iterator it ( mValues.find ( indx ) );
	return it != mValues.end() ? it->second : rDefaultValue;
}

const wxString &col::DatabaseItemData::operator[] ( wxUint32 idx ) const
{
	return GetColumnValue ( idx, empty_string );
}

bool col::DatabaseItemData::HasColumnValue ( const wxRegEx &rRe ) const
{
	wxASSERT ( rRe.IsValid() );
	
	foreach ( const ColumnValuePair &p, mValues )
	{
		if ( rRe.Matches ( p.second ) ) return true;
	}
	return false;
}

bool col::DatabaseItemData::HasColumnValue ( const wxString &rValCmp,
											bool caseSensitive ) const
{
	if ( caseSensitive )
	{
		foreach ( const ColumnValuePair &p, mValues )
		{
			if ( p.second == rValCmp ) return true;
		}
	}
	else
	{
		const wxString value_cmp ( rValCmp.Lower() );

		foreach ( const ColumnValuePair &p, mValues )
		{
			if ( p.second.Lower() == value_cmp ) return true;
		}
	}
	return false;
}

size_t col::DatabaseItemData::GetColumnCount() const
{
	return mValues.size(); 
}

void col::DatabaseItemData::Clear()
{
	mValues.clear();
}

void col::DatabaseItemData::ClearColumnUpdateHistory()
{
	mOutdatedColumns.clear();
}

///////////////////////////////////////
// DatabaseItemBase
///////////////////////////////////////

col::DatabaseItemBase::DatabaseItemBase ( const DatabaseTable *pTableDescr ) : 
mRuntimeId ( GenerateRuntimeId() ), mpParent(0), mpTableDescriptor ( pTableDescr )
{
	wxASSERT ( mpTableDescriptor );
}

col::DatabaseItemBase::DatabaseItemBase ( const DatabaseItemBase &rCpy ) 
: mRuntimeId ( rCpy.mRuntimeId ), mData ( rCpy.mData ), mState ( rCpy.mState ),
mpParent ( rCpy.mpParent ), mpTableDescriptor ( rCpy.mpTableDescriptor )
{
	foreach ( const DatabaseItemBasePtr child, rCpy.mChildren )
	{
		mChildren.push_back ( DatabaseItemBasePtr ( child->Clone() ) );
		mChildren.back()->SetParent ( this );
	}
}

col::DatabaseItemBase::~DatabaseItemBase() 
{ 
}

col::DatabaseItemBase &col::DatabaseItemBase::operator= ( const DatabaseItemBase &rCpy )
{
	if ( this == &rCpy ) return *this;

	ClearChildren();
	mData.Clear();

	mRuntimeId = rCpy.mRuntimeId;
	mData = rCpy.mData;
	mState = rCpy.mState;
	mpParent = rCpy.mpParent;
	mpTableDescriptor = rCpy.mpTableDescriptor;

	foreach ( const DatabaseItemBasePtr child, rCpy.mChildren )
	{
		mChildren.push_back ( DatabaseItemBasePtr ( child->Clone() ) );
		mChildren.back()->SetParent ( this );
	}
	return *this;
}

const col::DatabaseTable &col::DatabaseItemBase::GetTableDescriptor() const
{
	wxASSERT ( mpTableDescriptor );
	return *mpTableDescriptor;
}

void col::DatabaseItemBase::SetAsTrashed ( bool trashed ) 
{
	const Column *it = 
		GetTableDescriptor().FindColumn ( "is_trashed" );
	wxASSERT_MSG ( it, "This item is not trashable!" );
	if ( it )
		SetColumnValue ( it->GetIndex(), trashed );
}

bool col::DatabaseItemBase::IsNew() const
{
	return mState.IsActive ( DatabaseItemState::Added );
}

bool col::DatabaseItemBase::IsChanged() const
{
	return mState.IsActive ( DatabaseItemState::Changed );
}

bool col::DatabaseItemBase::IsDeleted() const
{
	return mState.IsActive ( DatabaseItemState::Deleted );
}

col::DatabaseItemState &col::DatabaseItemBase::GetState() 
{ 
	return mState; 
}

const col::DatabaseItemState &col::DatabaseItemBase::GetState() const 
{ 
	return mState; 
}

bool col::DatabaseItemBase::IsTrashed() const
{
	if ( const Column *it = GetTableDescriptor().FindColumn ( "is_trashed" ) )
	{
		const wxString &val = GetColumnValue ( it->GetIndex() );
		long b = -1;

		if ( !val.empty() && val.ToLong ( &b ) )
		{
			return b != 0;
		}
	}
	return false;
}

bool col::DatabaseItemBase::SetColumnValue ( wxUint32 indx, long value )
{
	try
	{
		return SetColumnValue ( indx, wxString() << value );
	}
	catch ( boost::bad_lexical_cast &rE )
	{
		wxFAIL_MSG ( rE.what() );
	}
	return false;
}

bool col::DatabaseItemBase::SetColumnValue ( wxUint32 indx, const wxString &rValue )
{
	if ( mData.SetColumnValue ( indx, rValue ) ) 
	{
		mState += DatabaseItemState::Changed;
		return true;
	}
	return false;
}

wxUint32 col::DatabaseItemBase::CopyData
( DatabaseItemBase *pTo, const std::deque < wxUint32 > &rIgnoreCols ) const
{
	wxUint32 change_count = 0;

	const DatabaseTable &tb = GetTableDescriptor();

	wxASSERT ( pTo->GetTableDescriptor() == tb );

	foreach ( const Column &c, tb.GetColumns() )
	{
		if ( !c.IsInternalColumn() && std::find ( rIgnoreCols.begin(), 
			rIgnoreCols.end(), c.GetIndex() ) == rIgnoreCols.end() )
		{
			if ( pTo->SetColumnValue ( c.GetIndex(), mData[c.GetIndex()] ) )
			{
				++change_count;
			}
		}
	}
	/*
	wxLogDebug ( "%s(%d)::CopyData ( rTo = %s(%d) ): "\
		"Changed column count %d.", tb.GetItemName().c_str(), mRuntimeId,
		pTo->GetTableDescriptor().GetItemName().c_str(), pTo->GetId(),
		change_count );
	*/
	return change_count;
}

const wxString &col::DatabaseItemBase::GetColumnValue 
( wxUint32 indx, const wxString &rDefaultValue ) const
{
	return mData.GetColumnValue ( indx, rDefaultValue );
}

const wxString &col::DatabaseItemBase::operator[] ( wxUint32 idx ) const
{
	return mData[idx];
}

col::DatabaseItemId col::DatabaseItemBase::GetId() const 
{ 
	return mRuntimeId; 
}

col::DatabaseItemId col::DatabaseItemBase::GetParentId() const 
{ 
	return mpParent ? mpParent->GetId() : InvalidItemId;
}

size_t col::DatabaseItemBase::GetColumnCount() const 
{ 
	return mData.GetColumnCount();
}

const col::DatabaseItemBase &col::DatabaseItemBase::GetParent() const
{
	wxASSERT_MSG ( mpParent, "Invalid parent item!" );
	return *mpParent;
}

void col::DatabaseItemBase::SetParent ( DatabaseItemBase *pItem )
{
	mpParent = pItem;
}

void col::DatabaseItemBase::SetId ( DatabaseItemId id )
{
	wxASSERT ( id != InvalidItemId );
	mRuntimeId = id;
}

void col::DatabaseItemBase::SetDatabaseId ( DatabaseItemId id, bool failOnInvalidId )
{
	if ( failOnInvalidId && id == InvalidItemId )
	{
		wxFAIL_MSG ( "Invalid database ID!" );
		return;
	}
	wxASSERT ( !GetTableDescriptor().GetIdColumn().IsInvalid() );
	mData.SetColumnValue ( GetTableDescriptor().GetIdColumn().GetIndex(), id );
}

col::DatabaseItemId col::DatabaseItemBase::GetDatabaseId ( bool failOnError ) const
{
	DatabaseItemId id = InvalidItemId;
	const Column &c = GetTableDescriptor().GetIdColumn();
	wxASSERT ( !c.IsInvalid() );
	if ( !mData[c.GetIndex()].ToLong ( &id ) && failOnError )
	{
		wxFAIL;
	}
	return id;
}

col::DatabaseItemId col::DatabaseItemBase::GetParentDatabaseId ( bool failOnError ) const
{
	wxASSERT ( mpParent );
	if ( mpParent ) return mpParent->GetDatabaseId ( failOnError );
	return InvalidItemId;
}

bool col::DatabaseItemBase::operator == ( const DatabaseItemBase &rCmp ) const
{ 
	return *this == rCmp.mRuntimeId;
}

bool col::DatabaseItemBase::operator == ( DatabaseItemId id ) const
{
	return mRuntimeId == id;
}

col::DatabaseItemBase &col::DatabaseItemBase::operator << 
( wxSQLite3ResultSet &rColumnData )
{
	for ( wxInt32 i = 0; i < rColumnData.GetColumnCount(); ++i )
	{
		SetColumnValue ( i, rColumnData.GetAsString(i) );
	}
	return *this;
}

void col::DatabaseItemBase::SetState ( wxUint32 state )
{
	if ( state == DatabaseItemState::None )
	{
		mData.ClearColumnUpdateHistory();
	}
	mState = state;
}

col::DatabaseItemId col::DatabaseItemBase::GenerateRuntimeId()
{
	static wxUint32 id_counter = 1;
	return id_counter++;
}

bool col::DatabaseItemBase::HasColumnValue ( const wxRegEx &rRe ) const
{
	return mData.HasColumnValue ( rRe );
}

bool col::DatabaseItemBase::HasColumnValue ( const wxString &rValCmp,
											bool caseSensitive ) const
{
	return mData.HasColumnValue ( rValCmp, caseSensitive );
}

const col::DatabaseItemBase *col::DatabaseItemBase::GetFirstChild() const
{
	return !mChildren.empty() ? mChildren.begin()->get() : 0;
}

const wxString col::DatabaseItemBase::ToSqlUpdateQuery() const
{
	using namespace boost;
	using namespace std;

	wxString values;

	foreach ( const Column &col, GetTableDescriptor().GetColumns() )
	{
		if ( mData.IsColumnOutdated ( col.GetIndex() ) )
		{
			append_if ( values, ", ", not1 ( is_empty() ) ); 

			if ( col.GetType() == Column::Type::Integer )
			{
				const wxString &data = mData[col.GetIndex()];
				if ( data.empty() )
				{
					wxASSERT ( !col.HasConstraint ( Column::Constraint::NotNull ) && 
						!col.HasConstraint ( Column::Constraint::PrimaryKey ) );
					values += str ( format ( "%1% = NULL " ) % col.GetName() );
					continue;
				}
				values += str ( format ( "%1% = %2% " ) % col.GetName() %
					mData[col.GetIndex()] );
			}
			else
			{
				values += wxSQLite3StatementBuffer().Format ( "%s = '%q' ", 
					(const char *)col.GetName().c_str(), 
					(const char *)mData[col.GetIndex()].c_str() );
			}
		}
	}

	const wxUint32 parent_db_id = GetTableDescriptor().HasParentIdColumn() ? 
		GetParentDatabaseId ( true ) : 0;

	if ( !values.empty() && parent_db_id > 0 )
	{
		return str ( format ( "UPDATE %1% SET %2% WHERE %3% = "\
			"%4% AND %5% = %6%; " ) % GetTableDescriptor().GetName() %
			values % GetTableDescriptor().GetIdColumn().GetName() %
			GetDatabaseId ( true ) % GetTableDescriptor().GetParentIdColumn().GetName() % 
			parent_db_id );
	}
	else if ( !values.empty() )
	{
		return str ( format ( "UPDATE %1% SET %2% WHERE %3% = %4%; ") % 
			GetTableDescriptor().GetName() % values % 
			GetTableDescriptor().GetIdColumn().GetName() % GetDatabaseId ( true ) );
	}
	return empty_string;
}

const wxString col::DatabaseItemBase::ToSqlInsertQuery() const
{
	using namespace boost;

	wxString vals, cols;

	foreach ( const Column &col, GetTableDescriptor().GetColumns() )
	{
		append_if ( cols, ", ", not1 ( is_empty() ) ); 
		append_if ( vals, ", ", not1 ( is_empty() ) ); 

		cols += col.GetName();

		if ( col.IsIdColumn() )
		{
			vals += "NULL";
		}
		else if ( col.IsParentIdColumn() )
		{
			vals += wxString() << GetParentDatabaseId();
		}
		else if ( col.GetType() == Column::Type::Integer )
		{
			const wxString &data = mData[col.GetIndex()];
			if ( data.empty() ) 
			{
				vals += '0';
			}
			else
			{
				vals += data;
			}
		}
		else if ( col.GetType() == Column::Type::Text )
		{
			vals += wxSQLite3StatementBuffer().Format ( "'%q'",
				(const char *)mData[col.GetIndex()].c_str() );
		}
	}
	wxASSERT ( !cols.empty() && !vals.empty() );

	return ( str ( format ( ("INSERT INTO %1% ( %2% ) values ( %3% );") ) % 
		GetTableDescriptor().GetName() % cols % vals ) );
}

const wxString col::DatabaseItemBase::ToSqlDeleteChildrenQuery() const
{
	using namespace boost;

	if ( GetChildCount() == 0 ) return empty_string;

	return str ( format ( "DELETE FROM %1% WHERE %2% = %3%;") %
		GetFirstChild()->GetTableDescriptor().GetName() % 
		GetFirstChild()->GetTableDescriptor().GetParentIdColumn().GetName() % 
		GetDatabaseId ( true ) );
}

const wxString col::DatabaseItemBase::ToSqlDeleteQuery() const
{
	using namespace boost;

	if ( GetTableDescriptor().HasParentIdColumn() )
	{
		return str ( format ( "DELETE FROM %1% WHERE %2% = %3% AND %4% = %5%;" ) %
			GetTableDescriptor().GetName() % GetTableDescriptor().GetIdColumn().GetName() % 
			GetDatabaseId ( true ) % GetTableDescriptor().GetParentIdColumn().GetName() % 
			GetParentDatabaseId ( true ) );
	}
	return str ( format ( "DELETE FROM %1% WHERE %2% = %3%" ) %
		GetTableDescriptor().GetName() % GetTableDescriptor().GetIdColumn().GetName() % 
		GetDatabaseId ( true ) );
}

const col::DatabaseItemBase *col::DatabaseItemBase::GetLastChild() const 
{ 
	wxASSERT ( !mChildren.empty() );
	return mChildren.back().get();
}

const col::DatabaseItemBaseDeque &col::DatabaseItemBase::GetChildren() const 
{ 
	return mChildren; 
}

col::DatabaseItemBaseDeque &col::DatabaseItemBase::GetChildren() 
{ 
	return mChildren; 
}

void col::DatabaseItemBase::AddChild ( const DatabaseItemBase &rChild )
{
	DatabaseItemBasePtr clone ( rChild.Clone() );
	mChildren.push_back ( clone );
	clone->SetParent ( this );

	if ( clone->GetDatabaseId() == InvalidItemId )
	{
		clone->mState = DatabaseItemState::Added;
	}
	else
	{
		clone->mData.ClearColumnUpdateHistory();
		clone->mState = DatabaseItemState::None;
	}

	mState += DatabaseItemState::ChildAdded;

	foreach ( DatabaseItemBasePtr child, clone->GetChildren() )
	{
		child->SetParent ( &(*mChildren.back()) );
	}
}

size_t col::DatabaseItemBase::GetChildCount() const 
{ 
	return mChildren.size(); 
}

const col::DatabaseItemBase *col::DatabaseItemBase::FindChild ( wxUint32 childId ) const
{
	if ( mChildren.empty() ) return 0;

	DatabaseItemBaseDeque::const_iterator it = find_if ( mChildren.begin(), 
		mChildren.end(), std::bind2nd ( db_item_equal_id(), childId ) );
	return it != mChildren.end() ? it->get() : 0;
}

col::DatabaseItemBase *col::DatabaseItemBase::FindChild ( wxUint32 childId )
{
	if ( mChildren.empty() ) return 0;

	DatabaseItemBaseDeque::iterator it = find_if ( mChildren.begin(), 
		mChildren.end(), std::bind2nd ( db_item_equal_id(), childId ) );
	return it != mChildren.end() ? it->get() : 0;
}


void col::DatabaseItemBase::ClearChildren() 
{ 
	mChildren.clear();
}